/*
 * 文 件 名:  Top100Mp3Downloader.java
 * 版    权:   深圳埃思欧纳信息咨询有限公司版权所有. YYYY-YYYY,  All rights reserved
 * 描    述:   <描述>
 * 修 改 人:  honny.huang
 * 修改时间: 2011-3-15
 * 跟踪单号:  <跟踪单号>
 * 修改单号:  <修改单号>
 * 修改内容:  <修改内容>
 */
package com.jsoup;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

/**
 * <一句话功能简述> <功能详细描述>
 *
 * @author honny.huang
 * @version [版本号, 2011-3-15]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */

public class Top100Mp3Downloader 
{
	/**  
     * 给定歌曲列表页面,返回歌曲名称和加密id的键值对  
     *   
     * @param url  歌曲列表地址,如:http://www.top100.cn/artist/info-agr5dcqe.shtml  
     * @return 键值对  
     */  
    private Map<String, String> findIds(String url) 
    {   
      try {   
        	//创建 URL 对象,url将作为 URL 解析的 String
            URL u = new URL(url);   
            Document doc = Jsoup.parse(u, 1000 * 10);  //1000 * 10 代表的是timeoutMillis 
            Element listDiv = doc.getElementById("songsListDiv");   
            Elements uls = listDiv.getElementsByTag("ul");   
            Map<String, String> map = new HashMap<String, String>();   
            for (int i = 0; i < uls.size(); i++) 
            {   
                Element ul = uls.get(i);   
                Element hidden = ul.getElementById("hidValue");   
                String id = hidden.val();   
                Element li = ul.getElementsByAttributeValue("class", "l3").first();   
                Element href = li.getElementsByTag("a").first();   
                String name = href.attr("title");   
  
                map.put(name, id);   
            }   
            return map;   
        } catch (MalformedURLException e) //如果字符串指定未知协议
        {   
            e.printStackTrace();   
        } catch (IOException e) {   
            e.printStackTrace();   
        }   
        return null;   
    }   
  
    /**  
     * 从歌曲的加密id获取歌曲的下载页面,并分析得到下载地址  
     *   
     * @param id  加密id  
     * @return 歌曲下载页面地址  
     */  
    private String findDownPathById(String id) 
    {   
        if (id.startsWith("m"))
        {   // 所有id都是m开头   
            id = id.substring(1);   
        }   
        try {   
            URL url = new URL("http://www.top100.cn/download/download.aspx?Productid=" + id);   
            Document doc = Jsoup.parse(url, 1000 * 2);   
            Elements eles = doc.getElementsByAttributeValue("onclick","javascript:$(this).css('color','red');");   
            String path = null;   
            for (int i = 0; i < eles.size(); i++) 
            {   
                Element e = eles.get(i);   
                if (e.tagName().equals("a")) 
                {   
                    path = e.attr("href");   
                    break;   
                }   
            }   
            return path;   
        } catch (MalformedURLException e) {   
            e.printStackTrace();   
        } catch (IOException e) {   
            e.printStackTrace();   
        }   
        return null;   
    }   
  
    /**  
     * 从获取的下载地址获取歌曲内容  
     *   
     * @param dir  保存到目录  
     * @param name   歌曲名称  
     * @param path   歌曲下载地址  
     */  
    private void downByPath(String dir, String name, String path)
    {   
    	// 通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。如果给定字符串是空字符串，那么结果是空抽象路径名。 
        File parent = new File(dir);   
        if (!parent.exists()) 
        {   
        	try 
        	{
        		 parent.mkdirs();   
			} catch (Exception e) 
			{
				System.out.println("创建目录失败：" + " error makedirs URL " + dir);
			}
        }   
        // 根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。
        File mp3 = new File(parent, name + ".mp3");   
        try {   
        	/**
        	 *  通常，创建一个到 URL 的连接需要几个步骤： 
			 *	1、通过在 URL 上调用 openConnection 方法创建连接对象。 
			 *	2、处理设置参数和一般请求属性。 
			 *	3、使用 connect 方法建立到远程对象的实际连接。 
			 *	4、远程对象变为可用。远程对象的头字段和内容变为可访问。
        	 */
            URL url = new URL(path);   
            // 对影响到远程资源连接的参数进行操作
            HttpURLConnection con = (HttpURLConnection) url.openConnection(); 
            // 处理设置参数和一般请求属性
            // 调用HttpURLConnection.setRequestProperty来设置头信息
            // 属性"User-Agent"必须有，否则报错： "您的请求来路不正确或验证字串不符，无法提交。"  
            // 此处必须伪造referer,否则会自动返回首页.分析后,与cookie无关   
            con.setRequestProperty("User-Agent","Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon;)");   
            con.setRequestProperty("Accept-Encoding", "gzip");   
            con.setRequestProperty("referer", "http://www.top100.cn");  
            // con.setRequestProperty("Cookie", ""); 
            // 这样就可以设置Cookie信息了，可解决需要登录验证的页面爬取问题。
            // 如果打算使用 URL 连接进行输入，则将 DoInput 标志设置为 true
            con.setDoInput(true); 
            // 与资源交互；查询头字段和内容
            con.connect();   
            if (con.getResponseCode() == HttpURLConnection.HTTP_OK) 
            {  
            	// 返回从此打开的连接读取的输入流。 在读取返回的输入流时，如果在数据可供读取之前达到读入超时时间，则会抛出 SocketTimeoutException。
                InputStream is = con.getInputStream();
                // 构造一个新分配的 Byte 对象，以表示 String 参数所指示的 byte 值。该字符串以使用基数 10 的 parseByte 方法所使用的方式被转换成一个 byte 值。 
                byte[] b = new byte[1024 * 5];   
                int length = -1;   
                // 创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
                OutputStream os = new FileOutputStream(mp3);  
                // 从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节，则返回值 -1。
                while ((length = is.read(b)) != -1)
                {   
                	// 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。
                    os.write(b, 0, length);   
                }   
//                #  String contentType = conn.getContentType();  
//                #       if (contentType == null || !contentType.startsWith("text/"))  
//                #           throw new IOException(String.format("Unhandled content type \"%s\" on URL %s. Must be text/*",   
//                #                   contentType, url.toString()));  
//                #         
//                #       InputStream inStream = new BufferedInputStream(conn.getInputStream());  
//                #       String charSet = getCharsetFromContentType(contentType); // may be null, readInputStream deals with it  
//                #   
//                #       Document doc = readInputStream(inStream, charSet, url.toExternalForm());  
//                #       inStream.close();  
                os.flush();   
                os.close();   
                is.close();   
            } else 
            {   
                System.out.println("服务器返回:" + con.getResponseCode());   
                throw new IOException(con.getResponseCode() + " error loading URL " + url.toString()); 
            }   
        } catch (MalformedURLException e) {   
            e.printStackTrace();   
        } catch (IOException e) {   
            e.printStackTrace();   
        }   
  
    }   
  
    public static void main(String[] args) {   
        Top100Mp3Downloader m = new Top100Mp3Downloader();   
        String url = "http://www.top100.cn/artist/info-agr5dcqe.shtml";
        for (Map.Entry<String, String> e : m.findIds(url).entrySet()) 
        {   
            String name = e.getKey();   
            String path = m.findDownPathById(e.getValue());   
            m.downByPath("F:\\music\\files\\Michael Jackson", name, path);   
            System.out.println(name + " from " + path + " has down!");   
        }   
    }   
  
}  

